﻿// Copyright 2008 (c) Michael Reichenauer, SWEDEN. All rights reserved.
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PostSharp.Laos;

namespace PSMock.Implementation
{
	/// <summary>
	/// The "database" for mocked funcions for one Mocking scope specified during Setup scope.
	/// </summary>
	internal class MockedFunctions
	{
		// The list of all explicitly mocked functions
		private List<MockedFunction> specifiedFunctions = new List<MockedFunction>();

		// List of types for which all members will be mocked with default values
		private List<Type> types = new List<Type>();

		// List of instances, for which all members are mocked with default values
		private List<object> instances = new List<object>();


		/// <summary>
		/// Adds the type, for which all members are mocked.
		/// </summary>
		/// <param name="type">The type.</param>
		internal void AddType(Type type)
		{
			types.Add(type);
		}

		/// <summary>
		/// Adds the instance for which all members are mocked
		/// </summary>
		/// <param name="instance">The instance.</param>
		internal void AddInstance(object instance)
		{
			instances.Add(instance);
		}


		/// <summary>
		/// Adds the function, which should be mocked.
		/// </summary>
		/// <param name="mockedFunction">The mocked function.</param>
		internal void AddFunction(MockedFunction mockedFunction)
		{
			specifiedFunctions.Add(mockedFunction);
		}


		/// <summary>
		/// Finds the mocked method for the specified real function information.
		/// </summary>
		/// <param name="eventArgs">The <see cref="PostSharp.Laos.MethodExecutionEventArgs"/> instance containing the event data.</param>
		/// <returns>null if real function should not be mocked.</returns>
		internal MockedFunction FindMockedMethod(MethodInvocationEventArgs eventArgs, int methoddataToken)
		{

			MockedFunction mockedFunction = FindSpecifiedMethod(eventArgs, methoddataToken);

			if (mockedFunction == null)
			{
				// There was no specified mocked function.
				Type declaringType = Util.DeclaringType(eventArgs.Delegate.Method);

				// We check if all members for the 
				// instance are mocked or if all members for all instances of the containing 
				// type should be mocked
				if ((eventArgs.Delegate.Target != null && instances.Contains(eventArgs.Delegate.Target))
					|| types.Contains(declaringType))
				{
					// Return default value for a function (or void for method )
					mockedFunction = new MockedFunction();
					mockedFunction.ReturnValue = Util.DefaultValue(eventArgs.Delegate.Method);
				}
			}

			return mockedFunction;
		}



		/// <summary>
		/// Finds the specified method.
		/// </summary>
		/// <param name="eventArgs">The <see cref="PostSharp.Laos.MethodExecutionEventArgs"/> 
		/// instance containing the event data.</param>
		private MockedFunction FindSpecifiedMethod(MethodInvocationEventArgs eventArgs, int methoddataToken)
		{
			foreach (MockedFunction function in specifiedFunctions)
			{
				if (function.MetadataToken == methoddataToken)
				{
					if (function.Instance == null || eventArgs.Delegate.Target == function.Instance)
					{
						if (ConditionFulfilled(function, eventArgs))
						{
							return function;
						}
					}
				}
			}
			return null;
		}


		/// <summary>
		/// Check if conditions are specified and if so, if they are fulfilled
		/// </summary>
		/// <param name="mockedFunction">The mocked function.</param>
		/// <param name="eventArgs">The <see cref="PostSharp.Laos.MethodExecutionEventArgs"/> instance containing the event data.</param>
		private bool ConditionFulfilled(MockedFunction mockedFunction, MethodInvocationEventArgs eventArgs)
		{
			if (mockedFunction.Condition != null)
			{
				// Condition is specified, check if fulfilled
				return (bool)mockedFunction.Condition.DynamicInvoke(eventArgs.GetArgumentArray());
			}
			else 
			{
				// No condition specified, - (condition is fulfilled implicitly)
				return true;
			}
		}
	}
}
